library(tidyverse) # ecosystem of data science packages
library(sf) # simple features
library(usmap) # map of US with Alaska and Hawaii
library(ggiraph) # make ggplots interactive2020 U.S. Presidential Election Results
1 Description
The data visualization of this module is based on the map of electoral votes that appears in the following New York Times webpage (Nov 3, 2020):
Presidential Election Results 2020
https://www.nytimes.com/interactive/2020/11/03/us/elections/results-president.html
1.1 Details (datasheet)
- Topic(s):
- Politics
- Elections
- President
- U.S.
- Data:
- Size: medium data
- Format: data in CSV tables
- Requires merging data
- Graphic:
- Type: choropleth map
- Styles: ggplot, interactive ggiraph
- Interactive: yes
1.2 R Packages
2 Data
This modules requires the CSV file countypres_2000-2020.csv which is stored in the data/ folder accompanying the github repo of this qmd file.
2.1 Presidential Elections data
First we import the Presidential Election Returns 2000-2020 data. Keep in mind that we are only interested in the 2020 elections. Also, because the data has votes at the county level, we need to compute the total number of votes for each candidate by state, and determine the winner party.
# data of presidential results (2000-2020)
pres_results = read_csv("data/countypres_2000-2020.csv")Rows: 72617 Columns: 12
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (7): state, state_po, county_name, office, candidate, party, mode
dbl (5): year, county_fips, candidatevotes, totalvotes, version
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# party winner in every state, 2020
state_by_party = pres_results |>
filter(year == 2020) |>
group_by(state_po, party) |>
summarize(votes = sum(candidatevotes)) |>
slice_max(votes) |>
select(state_po, party)`summarise()` has grouped output by 'state_po'. You can override using the
`.groups` argument.
head(state_by_party)# A tibble: 6 × 2
# Groups: state_po [6]
state_po party
<chr> <chr>
1 AK REPUBLICAN
2 AL REPUBLICAN
3 AR REPUBLICAN
4 AZ DEMOCRAT
5 CA DEMOCRAT
6 CO DEMOCRAT
2.2 Merging Data
# merging tiles map data with winner party
us_states = us_map() |>
inner_join(state_by_party, by = c("abbr" = "state_po"))3 Graphics
Recall that the tiles_map data frame has the data to graph the electoral votes map. To plot this map with ggplot() all you have to do is add a geom_tile() layer, hence the name tiles map.
3.1 Map 1
Let’s start with a basic graph of the tiles map, plotting the tiles that correspond to each state.
ggplot(us_states) +
geom_sf(aes(fill = party))3.2 Map 2
Because tiles_map has been merged with state_by_party, we also have the winner party of each state. Therefore, we can map this variable to the fill aesthetic to color-code the tiles according to party
ggplot(us_states) +
geom_sf(aes(fill = party), color = "white") +
scale_fill_manual(values = c("#0077cc", "#cc2200"))3.3 Map 3
Adding labels of states (i.e. state abbreviation)
ggplot(us_states) +
geom_sf(aes(fill = party), color = "white") +
geom_sf_text(aes(label = abbr), size = 2, color = "white") +
scale_fill_manual(values = c("#0077cc", "#cc2200"))3.4 Map 4
Taking care of the rest of details
ggplot(us_states) +
geom_sf(aes(fill = party), color = "white") +
geom_sf_text(aes(label = abbr), size = 2, color = "white") +
scale_fill_manual(values = c("#0077cc", "#cc2200")) +
theme_void() +
theme(legend.position = "bottom",
legend.text = element_text(size = 6),
legend.key.size = unit(0.5, 'cm'),
legend.title = element_blank())3.5 Map 5
We can make the map interactive by using functions from "ggiraph". In this case, the function geom_sf_interactive() allows us to display a tooltip feature when we hover over a particular state, namely, the display of the winner party.
gg1 = ggplot(us_states) +
geom_sf_interactive(aes(fill = party, tooltip = party), color = "white") +
geom_sf_text(aes(label = abbr), size = 2, color = "white") +
scale_fill_manual(values = c("#0077cc", "#cc2200")) +
theme_void() +
theme(legend.position = "bottom",
legend.text = element_text(size = 6),
legend.key.size = unit(0.5, 'cm'),
legend.title = element_blank())
girafe(ggobj = gg1)3.6 Mpa 6
It would be better to have a more informative message displayed by the tooltip, for instance, to be able to display the name of the state, as well as the proportion of votes for each party. This requires more data manipulation so that we know the proportion of votes that each party obtained in each state.
The following pipeline gives us such a table:
# proportion of votes by party in each state
state_by_party_prop = pres_results |>
filter(year == 2020) |>
group_by(state_po, party) |>
summarize(votes = sum(candidatevotes)) |>
mutate(prop = round(100 * votes / sum(votes), 2)) |>
filter(party %in% c("DEMOCRAT", "REPUBLICAN")) |>
select(state_po, party, prop) |>
pivot_wider(names_from = party, values_from = prop) |>
mutate(winner = ifelse(DEMOCRAT - REPUBLICAN > 0, "DEM", "REP"))`summarise()` has grouped output by 'state_po'. You can override using the
`.groups` argument.
head(state_by_party_prop)# A tibble: 6 × 4
# Groups: state_po [6]
state_po DEMOCRAT REPUBLICAN winner
<chr> <dbl> <dbl> <chr>
1 AK 43.0 53.1 REP
2 AL 36.6 62.0 REP
3 AR 34.8 62.4 REP
4 AZ 49.4 49.1 DEM
5 CA 63.5 34.3 DEM
6 CO 55.4 41.9 DEM
As we’ve done before, the next step involves merging the us_map() data table with the state_by_party_prop information:
# merging tiles map data with proportion-of-votes data
us_states2 = us_map() |>
inner_join(state_by_party_prop, by = c("abbr" = "state_po"))We can create again the interactive plot specifying a tooltip feature that displays the full name of the State, together with the percentage of votes for both parties.
gg2 = ggplot(us_states2) +
geom_sf_interactive(
aes(fill = winner,
tooltip = sprintf("%s\nDem: %s%%\nRep: %s%%", full, DEMOCRAT, REPUBLICAN)),
color = "white") +
geom_sf_text(aes(label = abbr), size = 2, color = "white") +
scale_fill_manual(values = c("#0077cc", "#cc2200")) +
theme_void() +
theme(legend.position = "bottom",
legend.text = element_text(size = 6),
legend.key.size = unit(0.5, 'cm'),
legend.title = element_blank())
girafe(ggobj = gg2)